Skip to contentMethod: orderedCollectionChanged(Collection, Collection)
1: /*
2: * JOPA
3: * Copyright (C) 2024 Czech Technical University in Prague
4: *
5: * This library is free software; you can redistribute it and/or
6: * modify it under the terms of the GNU Lesser General Public
7: * License as published by the Free Software Foundation; either
8: * version 3.0 of the License, or (at your option) any later version.
9: *
10: * This library is distributed in the hope that it will be useful,
11: * but WITHOUT ANY WARRANTY; without even the implied warranty of
12: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13: * Lesser General Public License for more details.
14: *
15: * You should have received a copy of the GNU Lesser General Public
16: * License along with this library.
17: */
18: package cz.cvut.kbss.jopa.sessions.change;
19:
20: import cz.cvut.kbss.jopa.sessions.MetamodelProvider;
21: import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils;
22:
23: import java.util.*;
24:
25: /**
26: * Checks for changes in a collection. By changes it is understood:
27: * <pre>
28: * <ul>
29: * <li>Different number of elements,</li>
30: * <li>Different order of elements in a collection with deterministic ordering semantics (List, Queue,
31: * <b>NOT!</b> Set)</li>
32: * <li>Different elements in the collection</li>
33: * </ul>
34: * </pre>
35: */
36: class CollectionChangeDetector implements ChangeDetector {
37:
38: private final ChangeDetector changeDetector;
39:
40: private final MetamodelProvider metamodelProvider;
41:
42: CollectionChangeDetector(ChangeDetector changeDetector, MetamodelProvider metamodelProvider) {
43: this.changeDetector = changeDetector;
44: this.metamodelProvider = metamodelProvider;
45: }
46:
47: @Override
48: public boolean hasChanges(Object clone, Object original) {
49: assert clone != null;
50: assert original != null;
51:
52: Collection<?> origCol = (Collection<?>) original;
53: Collection<?> cloneCol = (Collection<?>) clone;
54: if (origCol.size() != cloneCol.size()) {
55: return true;
56: }
57: if (origCol.isEmpty()) {
58: return false;
59: }
60: if (origCol instanceof Set) {
61: return setChanged(cloneCol, origCol);
62: } else {
63: return orderedCollectionChanged(cloneCol, origCol);
64: }
65: }
66:
67: private boolean orderedCollectionChanged(Collection<?> clone, Collection<?> original) {
68: Iterator<?> itOrig = original.iterator();
69: Iterator<?> itClone = clone.iterator();
70: boolean changes = false;
71:• while (itOrig.hasNext() && !changes) {
72: final Object cl = itClone.next();
73: final Object orig = itOrig.next();
74: changes = changeDetector.hasChanges(cl, orig);
75: }
76: return changes;
77: }
78:
79: /**
80: * When checking for set changes, we have to make sure different element order does not mean a change.
81: * <p>
82: * Therefore, we first order the elements in a predictable way and then compare the elements.
83: */
84: private boolean setChanged(Collection<?> clone, Collection<?> original) {
85: assert !clone.isEmpty();
86: assert !original.isEmpty();
87:
88: final Class<?> elementType = clone.iterator().next().getClass();
89: final List<?> cloneList = new ArrayList<>(clone);
90: final Comparator<Object> comparator = getSetComparator(elementType);
91: cloneList.sort(comparator);
92: final List<?> originalList = new ArrayList<>(original);
93: originalList.sort(comparator);
94: return orderedCollectionChanged(cloneList, originalList);
95: }
96:
97: /**
98: * For managed types, the comparator uses identifier's hashCode, for all other types hashCode is used directly.
99: */
100: private Comparator<Object> getSetComparator(Class<?> elemType) {
101: if (metamodelProvider.isEntityType(elemType)) {
102: return (o1, o2) -> {
103: final Object keyOne = EntityPropertiesUtils.getIdentifier(o1, metamodelProvider.getMetamodel());
104: final Object keyTwo = EntityPropertiesUtils.getIdentifier(o2, metamodelProvider.getMetamodel());
105: if (keyOne == null || keyTwo == null) {
106: return 0;
107: }
108: return Integer.compare(keyOne.hashCode(), keyTwo.hashCode());
109: };
110: } else {
111: return Comparator.comparingInt(Object::hashCode);
112: }
113: }
114: }